﻿using com.nlf.calendar;
using CommandLine;
using PCore = PBIW.Core.Core;
using SysConsole = System.Console;

namespace PBIW.Console.Commands
{
    public static class CastCommand
    {
        [Verb("cast", HelpText = "生成卦例文件。")]
        public sealed class Options : IOption
        {
            [Option('u', "upper", HelpText = "上卦表达式。可以使用 年 、 月 、 日 、 时 代表年月日时数。", Required = true)]
            public string UpperHexagram { get; set; } = null!;

            [Option('l', "lower", HelpText = "下卦表达式。可以使用 年 、 月 、 日 、 时 代表年月日时数、 上 代表上卦卦数（未除八）。", Required = true)]
            public string LowerHexagram { get; set; } = null!;

            [Option('c', "changing", HelpText = "动爻表达式。可以使用 年 、 月 、 日 、 时 代表年月日时数、 上 、 下 代表上卦、下卦卦数（未除八）。", Required = true)]
            public string ChangingLine { get; set; } = null!;


            [Option('f', "file", HelpText = "要输出到的文件（若需要输出到文件）。")]
            public string? OutputFile { get; set; } = null;

            [Option("force", HelpText = "指定是否强制输出。若开启，则若指定的文件存在，将会覆盖。")]
            public bool Force { get; set; } = false;

            [Option('t', "time", HelpText = "指定起卦的时间。如不指定则使用当前时间。阳历如 Y2022/2/4/19/51 表示公历年月日时分，或如 G壬寅/壬寅/戊子/壬戌 以干支形式表示（六十年内），阴历如 N壬寅/1/4/未 或 N寅/1/4/未 表示年月日时（六十年内），其中若月为负数则表示闰月。")]
            public string? DateTime { get; set; } = null;
        }

        public sealed class Executor : IExecutor<Options>
        {
            private readonly PCore core;
            private static readonly IReadOnlyDictionary<string, int> BranchIndexDictionary
                = YiJingFramework.StemsAndBranches.EarthlyBranch.BuildStringBranchTable("C")
                .ToDictionary(item => item.s, item => item.branch.Index);
            public Executor(PCore core)
            {
                this.core = core;
            }

            private static Lunar? ParseSolar(string dateStr)
            {
                try
                {
                    var strs = dateStr[1..].Split('/');
                    if (strs.Length is not 5)
                        return null;

                    List<int> result = new List<int>(5);
                    foreach (var str in strs)
                    {
                        if (int.TryParse(str, out int i))
                            result.Add(i);
                        else
                            return null;
                    }
                    return new Solar(result[0], result[1], result[2], result[3], result[4], 0).getLunar();
                }
                catch
                {
                    return null;
                }
            }
            private static Lunar? ParseSolarInStemsAndBranches(string dateStr)
            {
                try
                {
                    var strs = dateStr[1..].Split('/');
                    if (strs.Length is not 4)
                        return null;
                    return Solar.fromBaZi(
                        strs[0], strs[1], strs[2], strs[3],
                        1, DateTime.Now.Year - 60)[0].getLunar();
                }
                catch
                {
                    return null;
                }
            }
            private static Lunar? ParseLunar(string dateStr)
            {
                try
                {
                    var strs = dateStr[1..].Split('/');
                    if (strs.Length is not 4)
                        return null;

                    var yearStr = strs[0];
                    if (!int.TryParse(strs[1], out int monthNum))
                        return null;
                    if (!int.TryParse(strs[2], out int dayNum))
                        return null;
                    if (!BranchIndexDictionary.ContainsKey(strs[3]))
                        return null;
                    var timeIndex = BranchIndexDictionary[strs[3]];

                    int day, hour;
                    if (timeIndex is 1)
                    {
                        day = dayNum - 1;
                        hour = 23;
                    }
                    else
                    {
                        day = dayNum;
                        hour = timeIndex * 2 - 3;
                    }

                    var year = DateTime.Today.Year;
                    bool got = false;
                    for (int i = 0; i < 60; i++)
                    {
                        try
                        {
                            var lunar = new Lunar(year, monthNum, day, hour, 0, 0);
                            if (lunar.getYearInGanZhi() == yearStr || lunar.getYearZhi() == yearStr)
                            {
                                got = true;
                                break;
                            }
                        }
                        catch (ArgumentOutOfRangeException e) when (e.Message.StartsWith("wrong lunar year"))
                        {
                        }
                        catch
                        {
                            return null;
                        }
                        year--;
                    }
                    if (!got)
                        return null;
                    return new Lunar(year, monthNum, day, hour, 0, 0);
                }
                catch
                {
                    return null;
                }
            }
            private static Lunar? ParseDateTime(string str)
            {
                try
                {
                    switch (str[0])
                    {
                        case 'Y':
                            return ParseSolar(str);
                        case 'N':
                            return ParseLunar(str);
                        case 'G':
                            return ParseSolarInStemsAndBranches(str);
                        default:
                            return null;
                    }
                }
                catch
                {
                    return null;
                }
            }
            public void Execute(Options options)
            {
                var lunar = options.DateTime is null ?
                    Lunar.fromDate(DateTime.Now) :
                    ParseDateTime(options.DateTime);

                if (lunar is null)
                {
                    SysConsole.ForegroundColor = ConsoleColor.DarkRed;
                    SysConsole.WriteLine($"日期格式错误，请检查。输入的日期：{options.DateTime}");
                    SysConsole.ResetColor();
                    return;
                }

                if (!this.core.GenerateText(lunar, options.UpperHexagram,
                    options.LowerHexagram, options.ChangingLine, null,
                    out string resultOrMessage))
                {
                    SysConsole.ForegroundColor = ConsoleColor.DarkRed;
                    SysConsole.WriteLine($"内容生成失败：{resultOrMessage}");
                    SysConsole.ResetColor();
                    return;
                }

                SysConsole.WriteLine($"生成内容如下：");
                SysConsole.WriteLine($"===============================");
                SysConsole.WriteLine(resultOrMessage);
                SysConsole.WriteLine($"===============================");

                if (options.OutputFile is null)
                {
                    SysConsole.WriteLine($"执行完毕。");
                    return;
                }

                if (!File.Exists(options.OutputFile) || options.Force)
                {
                    try
                    {
                        File.WriteAllText(options.OutputFile, resultOrMessage);
                    }
                    catch
                    {
                        SysConsole.ForegroundColor = ConsoleColor.DarkRed;
                        SysConsole.WriteLine($"文件写入失败，请检查文件访问权限。");
                        SysConsole.ResetColor();
                        return;
                    }
                    SysConsole.WriteLine($"文件生成完毕。");
                    return;
                }
                SysConsole.ForegroundColor = ConsoleColor.DarkRed;
                SysConsole.WriteLine($"文件 {options.OutputFile} 已存在。请删除后重试。");
                SysConsole.ResetColor();
                return;
            }
        }
    }
}